package common.server.flowcontrol.support;

import common.server.flowcontrol.FlowControl;
import com.google.common.util.concurrent.Monitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.TimeUnit;

/**
 * 线程并发流控
 * @author Administrator
 * @version [版本号, 2017/12/13 0013]
 * @see [相关类/方法]
 * @since [产品/模块版本]
 */
public class ConcurrencyFlowControl implements FlowControl {
	protected static final Logger logger = LoggerFactory.getLogger(ConcurrencyFlowControl.class);

	/**
	 * 最大访问量
	 */
	private static final int MAX_TOKENS_NUM = 50;

	/**
	 * 最大令牌数
	 */
	private final int maxTokens;

	/**
	 * 当前令牌
	 */
	private int currentTokens;

	/**
	 * 锁
	 */
	Monitor lock;

	/**
	 * 获取条件
	 */
	Monitor.Guard cquireWhere;

	/**
	 * 释放条件
	 */
	Monitor.Guard releaseWhere;

	/**
	 *
	 * @param tokens
	 */
	public ConcurrencyFlowControl(int tokens) {
		if (tokens <= 0){
            this.maxTokens = MAX_TOKENS_NUM;
        }else{
            this.maxTokens = tokens;
        }

		this.currentTokens = this.maxTokens;
		this.lock = new Monitor();
		// 初始化获取条件
		this.cquireWhere = new Monitor.Guard(this.lock) {
			@Override
			public boolean isSatisfied() {
				return currentTokens > 0;
			}
		};
		// 初始化释放条件
		this.releaseWhere = new Monitor.Guard(this.lock) {
			@Override
			public boolean isSatisfied() {
				return currentTokens < maxTokens;
			}
		};
	}

	@Override
	public void syncAcquire() {
		try {
			this.lock.enterWhen(this.cquireWhere);
			// 令牌減一
			currentTokens--;
            logger.debug("acquire tokens success! has tokens:{}", currentTokens);
		} catch (InterruptedException e) {
			throw new RuntimeException(e);
		} finally {
			this.lock.leave();
		}
	}

	@Override
	public boolean acquire() {
		throw new RuntimeException("This method is empty!");
	}

	@Override
	public void release() {
		try {
			this.lock.enterWhen(this.releaseWhere);
			// 令牌释放
			currentTokens++;
            logger.debug("release tokens success! has tokens:{}", currentTokens);
		} catch (InterruptedException e) {
			throw new RuntimeException(e);
		} finally {
			this.lock.leave();
		}
	}

	@Override
	public boolean acquire(long timeout, TimeUnit unit) {
		throw new RuntimeException("This method is empty!");
	}
}
